/**
 * 
 */
package net.phoenix.repository.query.xpath;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.Workspace;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.jcr.query.RowIterator;

import net.phoenix.repository.BasicNode;
import net.phoenix.repository.BasicSession;
import net.phoenix.repository.JdbcSession;
import net.phoenix.repository.RepositoryRuntimeException;
import net.phoenix.repository.SQLParameter;
import net.phoenix.repository.dao.NodeTableCountDao;
import net.phoenix.repository.dao.NodeTableQueryDao;
import net.phoenix.repository.data.NodeData;
import net.phoenix.repository.query.GeneralQuery;

import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.XPathReader;
import org.jaxen.saxpath.helpers.XPathReaderFactory;

/**
 * @author lixf
 * 
 */
public class XPathQuery extends GeneralQuery {
	private String statement;
	private long limit;
	private long offset;
	private BasicSession session;
	private SQLParameter[] parameters;
	private String querySQL;
	private String countSQL;

	public XPathQuery() {
	}

	public void init(Workspace workspace, String statement)
			throws InvalidQueryException {
		super.init(workspace, statement);
		this.statement = statement;
		this.limit = 20;
		this.offset = 0;
		this.session = (BasicSession) workspace.getSession();
		try {
			XPathReader reader = XPathReaderFactory.createReader();
			XPath2SQL handler = new XPath2SQL(workspace.getSession()
					.getRootNode());
			reader.setXPathHandler(handler);
			reader.parse(statement);
			this.parameters = prepareParameters(handler.getParameters());
			this.querySQL = handler.getQuerySQL();
			this.countSQL = handler.getCountSQL();
		} catch (SAXPathException e) {
			throw new InvalidQueryException(e);
		} catch (RepositoryException e) {
			throw new InvalidQueryException(e);
		}

	}

	public XPathQuery(BasicNode node, String statement)
			throws RepositoryException {
		super.init(node.getSession().getWorkspace(), statement);
		this.statement = statement;
		this.limit = 20;
		this.offset = 0;
		this.session = (BasicSession) workspace.getSession();
		try {
			XPathReader reader = XPathReaderFactory.createReader();
			XPath2SQL handler = new XPath2SQL(node);
			reader.setXPathHandler(handler);
			reader.parse(statement);
			this.parameters = prepareParameters(handler.getParameters());
			this.querySQL = handler.getQuerySQL();
			this.countSQL = handler.getCountSQL();
		} catch (SAXPathException e) {
			throw new RepositoryException(e);
		}

	}

	@Override
	public QueryResult execute() throws InvalidQueryException,
			RepositoryException {
		return new XPathQueryResult();
	}

	@Override
	public void setLimit(long limit) {
		this.limit = limit;
	}

	@Override
	public void setOffset(long offset) {
		this.offset = offset;
	}

	@Override
	public String getStatement() {
		return this.statement;
	}

	@Override
	@SuppressWarnings("deprecation")
	public String getLanguage() {
		return Query.XPATH;
	}

	@Override
	public void bindValue(String varName, Value value)
			throws IllegalArgumentException, RepositoryException {
		throw new UnsupportedOperationException("bindValue");

	}

	@Override
	public String[] getBindVariableNames() throws RepositoryException {
		return new String[0];
	}

	private SQLParameter[] prepareParameters(List<Value> parameters)
			throws ValueFormatException, IllegalStateException,
			RepositoryException {
		SQLParameter[] params = new SQLParameter[parameters.size()];
		for (int i = 0; i < parameters.size(); i++) {
			Value value = parameters.get(i);
			SQLParameter param = new SQLParameter();
			switch (value.getType()) {
			case PropertyType.BINARY:
				param.setValue(value.getString());
				break;
			case PropertyType.BOOLEAN:
				param.setValue(value.getBoolean());
				break;
			case PropertyType.DATE:
				param.setValue(new Timestamp(value.getDate().getTimeInMillis()));
				break;
			case PropertyType.DECIMAL:
				param.setValue(value.getString());
				break;
			case PropertyType.DOUBLE:
				param.setValue(value.getString());
				break;
			case PropertyType.LONG:
				param.setValue(value.getString());
				break;
			case PropertyType.NAME:
				param.setValue(value.getString());
				break;
			case PropertyType.PATH:
				param.setValue(value.getString());
				break;
			case PropertyType.REFERENCE:
				param.setValue(value.getString());
				break;
			case PropertyType.STRING:
				param.setValue(value.getString());
				break;
			default:
				param.setValue(value.getString());
				break;
			}
			params[i] = param;
		}
		return params;
	}

	class XPathQueryResult implements QueryResult {
		private long size;

		public XPathQueryResult() throws RepositoryException {
			NodeTableCountDao dao = session.getDao(NodeTableCountDao.class);
			try {
				this.size = dao.count(countSQL, parameters);
			} catch (SQLException e) {
				throw new RepositoryException(e);
			}
		}

		@Override
		public String[] getColumnNames() throws RepositoryException {
			return new String[0];
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.jcr.query.QueryResult#getRows()
		 */
		@Override
		public RowIterator getRows() throws RepositoryException {
			throw new UnsupportedOperationException("getRows");
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.jcr.query.QueryResult#getNodes()
		 */
		@Override
		public NodeIterator getNodes() throws RepositoryException {
			return new XPathNodeIterator();
		}

		@Override
		public String[] getSelectorNames() throws RepositoryException {
			return new String[0];
		}

		class XPathNodeIterator implements NodeIterator {
			private long position;
			private Iterator<Node> nodes;
			private int pageSize = 20;

			public XPathNodeIterator() {
				position = offset;
				nodes = new ArrayList<Node>().iterator();
			}

			@Override
			public void skip(long skipNum) {
				position += skipNum;
				if (position - offset > limit)
					throw new IllegalArgumentException("The skip number "
							+ skipNum + " exceeds the limit " + limit + " .");
			}

			@Override
			public long getSize() {
				return size;
			}

			@Override
			public long getPosition() {
				return this.position;
			}

			@Override
			public boolean hasNext() {
				return position < size;
			}

			@Override
			public Object next() {
				return this.nextNode();
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}

			@Override
			public Node nextNode() {
				if (nodes.hasNext()) {
					position++;
					return nodes.next();
				}
				NodeTableQueryDao dao = session.getDao(NodeTableQueryDao.class);
				try {
					NodeData[] data = dao.search(querySQL, parameters,
							position, pageSize);
					Vector<Node> vnodes = new Vector<Node>();
					for (int i = 0; i < data.length; i++) {
						vnodes.add(new BasicNode((JdbcSession) workspace
								.getSession(), data[i]));
					}
					this.nodes = vnodes.iterator();
				} catch (Exception ex) {
					throw new RepositoryRuntimeException(ex);
				}

				if (nodes.hasNext()) {
					position++;
					return nodes.next();
				}
				return null;
			}

		}
	}
}
